package com.boil.qz.safekavass.aspect;

import com.boil.qz.safekavass.model.LoginUser;
import com.boil.qz.safekavass.pojo.MenuVo;
import com.boil.qz.safekavass.pojo.RoleResourceVo;
import com.boil.qz.safekavass.service.PermissionService;
import com.boil.qz.safekavass.service.ResourceService;
import com.boil.qz.safekavass.service.ShiroFilerChainManager;
import com.boil.util.Constants;
import com.boil.util.ShiroUtils;
import com.boil.util.Utils;
import org.apache.shiro.session.Session;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by ChenYong on 2017-08-28.
 * <br>
 * 权限切面组件。
 */
@Component
@Aspect
@Order(Constants.VALUE_1)
public class PermissionAspect {
    /**
     * 日志
     */
    private static Logger log = LoggerFactory.getLogger(PermissionAspect.class);
    /**
     * 注入资源 Service
     */
    @Autowired
    private ResourceService resourceService;
    /**
     * 注入权限 Service
     */
    @Autowired
    private PermissionService permissionService;
    /**
     * 注入 Shiro 过滤器链管理器
     */
    @Autowired
    private ShiroFilerChainManager shiroFilerChainManager;
    /**
     * 注入 Spring 线程池
     */
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 定义切入点。
     */
    @Pointcut("execution(* com.boil.qz.safekavass.service.RoleService.save(..)) || " +
            "execution(* com.boil.qz.safekavass.service.RoleService.update(..)) || " +
            "execution(* com.boil.qz.safekavass.service.RoleService.deleteById(..)) || " +
            "execution(* com.boil.qz.safekavass.service.ResourceService.save(..)) || " +
            "execution(* com.boil.qz.safekavass.service.ResourceService.update(..)) || " +
            "execution(* com.boil.qz.safekavass.service.ResourceService.deleteById(..)) || " +
            "execution(* com.boil.qz.safekavass.service.PermissionService.doPermission(..))")
    public void pointcut() {
    }

    /**
     * 执行顺序：1。
     * <br>
     * 当目标方法开始执行时，可以执行拦截操作，阻止目标方法执行。
     *
     * @param proceedingJoinPoint 连接点实例
     * @return 实例
     * @throws Throwable 抛出异常
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return proceedingJoinPoint.proceed();
    }

    /**
     * 执行顺序：2。
     * <br>
     * 当目标方法开始执行时，执行 before 方法。
     *
     * @param joinPoint 连接点实例
     */
    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint) {
    }

    /**
     * 执行顺序：3。
     * <br>
     * 当目标方法抛出异常时，执行 afterThrowing 方法。
     *
     * @param joinPoint 连接点
     * @param error     异常
     */
    @AfterThrowing(pointcut = "pointcut()", throwing = "error")
    public void afterThrowing(JoinPoint joinPoint, Throwable error) {
    }

    /**
     * 执行顺序：4。
     * <br>
     * 当目标方法成功/失败执行完毕时，都会执行 after 方法。
     *
     * @param joinPoint 连接点实例
     */
    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
    }

    /**
     * 执行顺序：5。
     * <br>
     * 当目标方法成功执行完毕时，执行 afterReturning 方法。
     *
     * @param joinPoint 连接点实例
     * @param returnVal 返回值
     */
    @AfterReturning(pointcut = "pointcut()", returning = "returnVal")
    public void afterReturning(JoinPoint joinPoint, Object returnVal) {
        // 执行以下操作：
        // 1、初始化过滤器链
        threadPoolTaskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                shiroFilerChainManager.initFilterChains();
            }
        });

        // 2、清空授权缓存，使 Shiro 重新加载权限；
        threadPoolTaskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                ShiroUtils.clearAuthorizationCache();
            }
        });

        // 3、刷新在线用户 session 中的数据。
        for (final Session session : Constants.ONLINE_SESSION_CACHE) {
            threadPoolTaskExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 获取登录用户
                        LoginUser loginUser = (LoginUser) session.getAttribute(Constants.LOGIN_USER);

                        // 3.1、刷新登录用户菜单
                        // 获取登录用户的菜单
                        List<MenuVo> loginUserMenus = resourceService.getUserMenus(loginUser.getId());

                        // 将登录用户的所有菜单存入 Session 中
                        session.setAttribute(Constants.LOGIN_USER_MENUS, loginUserMenus);

                        // 3.2、刷新登录用户角色、权限
                        // 查询登录用户的角色资源关联
                        List<RoleResourceVo> roleResourceVos = permissionService.findRoleResourceByUserId(loginUser.getId());
                        // 登录用户拥有的角色
                        Set<String> loginUserRoles = new HashSet<String>();
                        // 登录用户拥有的权限
                        Set<String> loginUserPerms = new HashSet<String>();

                        // 整理角色和权限
                        for (RoleResourceVo roleResourceVo : roleResourceVos) {
                            loginUserRoles.add(roleResourceVo.getRoleCode());
                            loginUserPerms.add(roleResourceVo.getPermissionCode());
                        }

                        // 将登录用户的所有角色存入 session 中
                        session.setAttribute(Constants.LOGIN_USER_ROLES, Utils.parseList2Str(new ArrayList<String>(loginUserRoles), Constants.SPLIT_1));
                        // 将登录用户的所有权限存入 session 中
                        session.setAttribute(Constants.LOGIN_USER_PERMS, Utils.parseList2Str(new ArrayList<String>(loginUserPerms), Constants.SPLIT_1));

                        log.info("账号：{}--->刷新菜单数量：{}，刷新角色数量：{}，刷新权限数量：{}", loginUser.getLoginid(), loginUserMenus.size(), loginUserRoles.size(), loginUserPerms.size());
                    } catch (Exception e) {
                        log.error("PermissionAop【权限切面组件】，方法：afterReturning--->刷新用户菜单出现异常：{}", e);
                    }
                }
            });
        }
    }
}